/**
 * Render Bender - A Flash-based chained Pixel Bender kernel previewer
 * Copyright (C) 2009  David Lenaerts
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Author information:
 * David Lenaerts
 * url: http://www.derschmale.com
 * mail: david.lenaerts@gmail.com
 * 
 */
 
package com.derschmale.renderBender.model.vo
{
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Loader;
	import flash.display.LoaderInfo;
	import flash.display.Shader;
	import flash.display.ShaderJob;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.ShaderEvent;
	import flash.filters.ShaderFilter;
	import flash.geom.Point;
	import flash.net.URLLoader;
	import flash.net.URLLoaderDataFormat;
	import flash.net.URLRequest;
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	
	import mx.collections.ArrayCollection;
	
	public class KernelSequence extends EventDispatcher
	{
		public static const READY : String = "ready";
		public static const EXECUTION_COMPLETE : String = "executionComplete";
		
		public var forceWait : Boolean = true;
		
		private var _loadCue : Vector.<AssetDefinition>;
		
		private var _assets : Array;
		private var _assetDefinitions : Array;
		private var _kernelDefinitions : Vector.<KernelDefinition>;
		private var _loadingIndex : int;
		
		// ArrayCollection for easier binding in preview screens
		private var _shaders : ArrayCollection;
		private var _filters : Vector.<ShaderFilter>;
		
		private var _originals : Array;
		private var _previewables : ArrayCollection;
		
		private var _kernelCounter : int;
		
		private var _isReady : Boolean;
		
		public function KernelSequence()
		{
			super();
		}
		
		public function setBitmapDataAsset(id : String, bitmapData : BitmapData) : void
		{
			var data : ImageData;
			if (_assets[id] is BitmapData) {
				_assets[id].dispose();
				_assets[id] = bitmapData;
				_originals[id].dispose();
				_originals[id] = bitmapData.clone();
				
				for (var i : int = 0; i < _previewables.length; i++) {
					data = _previewables.getItemAt(i) as ImageData;
					if (data.definition.id == id) {
						data.bitmap.bitmapData = bitmapData;
					}
				}
			}
		}
		
		public function reset() : void
		{
			var src : BitmapData;
			var dst : Object;
			
			for (var key : String in _assets) {
				dst = _assets[key];
				if (dst is BitmapData) {
					src = BitmapData(_originals[key])
					dst.copyPixels(src, src.rect, new Point());
				}
				else if (dst is Vector.<Number>) {
					createVector(_assetDefinitions[key]);
				}
				else {
					createByteArray(_assetDefinitions[key]);
				}
			}
		}
		
		public function execute() : void
		{
			for (var i : int = 0; i < _previewables.length; i++) {
				ImageData(_previewables.getItemAt(i)).bitmap.bitmapData.lock();
			}
			_isReady = false;
			_kernelCounter = 0;
			runNextKernel();
		}
		
		private function runNextKernel() : void
		{
			var shader : Shader = Shader(_shaders.getItemAt(_kernelCounter));
			var def : KernelDefinition = _kernelDefinitions[_kernelCounter];
			var targetDef : AssetDefinition = _assetDefinitions[def.output.assetId];
			var filter : ShaderFilter;
			var target : Object = _assets[def.output.assetId];
			var src : Object;
			var imgDef : KernelImageDefinition;
			
			//DebugPanel.trace("Running kernel: "+_kernelCounter+ " ("+_shaders[_kernelCounter].data.name+")");
			
			for (var i : int = 0; i < def.inputs.length; i++) {
				imgDef = def.inputs[i];
				src = _assets[imgDef.assetId];
				shader.data[imgDef.name].input = src;
				if (!(src is BitmapData)) {
					var assetDef : AssetDefinition = _assetDefinitions[imgDef.assetId];
					shader.data[imgDef.name].width = assetDef.width;
					shader.data[imgDef.name].height = assetDef.height;
				} 
			}
			
			if (def.method == KernelDefinition.APPLY_FILTER_METHOD) {
				// currently not supported
				//BitmapData(target).applyFilter(
			}
			else {
				var shaderJob : ShaderJob = new ShaderJob(shader, target, targetDef.width, targetDef.height);
				if (forceWait) {
					shaderJob.start(true);
					onKernelComplete(null);
				}
				else {
					shaderJob.addEventListener(ShaderEvent.COMPLETE, onKernelComplete);
					shaderJob.start(false);
				}
			}
		}
		
		private function onKernelComplete(event : ShaderEvent) : void
		{
			++_kernelCounter;
			
			if (_kernelCounter == _shaders.length) {
				_isReady = true;
				for (var i : int = 0; i < _previewables.length; i++) {
					ImageData(_previewables.getItemAt(i)).bitmap.bitmapData.unlock();
				}
				dispatchEvent(new Event(EXECUTION_COMPLETE));
			}
			else {
				runNextKernel();
			}
		}
		
		public function get isReady() : Boolean
		{
			return _isReady;
		}
		
		public function create(assetDefs : Vector.<AssetDefinition>, kernelDefs : Vector.<KernelDefinition>) : void
		{
 			if (_previewables) {
				for (var i : int = 0; i < _previewables.length; i++) {
					ImageData(_previewables.getItemAt(i)).bitmap.bitmapData.dispose();
				}
			}
			createAssets(assetDefs);
			createKernels(kernelDefs);
		}
		
		public function get shaders() : ArrayCollection
		{
			return _shaders;
		}
		
		// used for previews
		public function get previewables() : ArrayCollection
		{
			return _previewables;
		}
		
		private function createAssets(assetDefs : Vector.<AssetDefinition>) : void
		{
			var data : ImageData;
			
			_loadCue = new Vector.<AssetDefinition>();
			_assets = [];
			_assetDefinitions = [];
			_previewables = new ArrayCollection();
			_originals = [];
			
			for (var i : int = 0; i < assetDefs.length; i++) {
				switch (assetDefs[i].type) {
					case "BitmapData":
						data = new ImageData();
						data.definition = assetDefs[i];
						_previewables.addItem(data);
						createBitmapData(assetDefs[i]);
						break;
					case "ByteArray":
						createByteArray(assetDefs[i]);
						break;
					case "Vector":
						createVector(assetDefs[i]);
						break;
				}
				_assetDefinitions[assetDefs[i].id] = assetDefs[i];
			}
		}
		
		// kernels need to be loaded... remember?
		private function createKernels(kernelDefs : Vector.<KernelDefinition>) : void
		{
			_shaders = new ArrayCollection();
			_filters = new Vector.<ShaderFilter>(kernelDefs.length);
			_kernelDefinitions = kernelDefs;
			
			_loadingIndex = 0;
			
			loadNextKernel();
		}
		
		private function loadNextKernel() : void
		{
			var loader : URLLoader = new URLLoader();
			loader.dataFormat = URLLoaderDataFormat.BINARY;
			loader.addEventListener(Event.COMPLETE, onLoadKernelComplete);
			loader.load(new URLRequest(_kernelDefinitions[_loadingIndex].src));
		}
		
		private function onLoadKernelComplete(event : Event) : void
		{
			var shader : Shader;
			var filter : ShaderFilter;
			var def : KernelDefinition =  _kernelDefinitions[_loadingIndex];
			
			shader = new Shader(URLLoader(event.target).data);
			_shaders.addItem(shader);
			
			if (def.method == KernelDefinition.APPLY_FILTER_METHOD) {
				filter = new ShaderFilter(shader);
				_filters[_loadingIndex] = filter;
			}
			
			++_loadingIndex;
			if (_loadingIndex == _kernelDefinitions.length) {
				_loadingIndex = 0;
				if (_loadCue.length > 0) loadNextImage();
				else {
					prepPreviewables();
					_isReady = true;
					dispatchEvent(new Event(READY));
				}
			}
			else {
				loadNextKernel();
			}
		}
		
		private function createBitmapData(def : AssetDefinition) : void
		{
			var bmp : BitmapData;
			
			if (def.src && def.src != "") {
				// cue for loading later
				_loadCue.push(def);
			}
			else {
				bmp = new BitmapData(def.width, def.height, (def.channels == 4), 0);
				_assets[def.id] = bmp;
			}
		}
		
		private function createByteArray(def : AssetDefinition) : void
		{
			var ba : ByteArray = new ByteArray();
			var len : int = def.width*def.height*def.channels;
			ba.endian = Endian.LITTLE_ENDIAN;
			for (var i : int = 0; i < len; i++) {
				ba.writeDouble(0);
			}
			
			_assets[def.id] = ba;
		}
		
		private function createVector(def : AssetDefinition) : void
		{
			var v : Vector.<Number>;
			var len : int = def.width*def.height*def.channels;
			
			v = new Vector.<Number>(len);
			
			for (var i : int = 0; i < len; i++) {
				v[i] = 0;
			}
			
			_assets[def.id] = v;
		}
		
		private function loadNextImage() : void
		{
			var def : AssetDefinition = _loadCue[_loadingIndex];
			var loader : Loader = new Loader();
			loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadImageComplete);
			loader.load(new URLRequest(def.src));
		}
		
		private function onLoadImageComplete(event : Event) : void
		{
			var content : Bitmap = LoaderInfo(event.target).content as Bitmap;
			var id : String = _loadCue[_loadingIndex].id;
			_assets[id] = content.bitmapData;
			++_loadingIndex;
			if (_loadingIndex == _loadCue.length) {
				prepPreviewables();
				_isReady = true;
				dispatchEvent(new Event(READY));
			}
			else
				loadNextImage();
		}
		
		private function prepPreviewables() : void
		{
			var data : ImageData;
			for (var i : int = 0; i < _previewables.length; i++) {
				data = ImageData(_previewables.getItemAt(i));
				data.bitmap = new Bitmap(_assets[data.definition.id]);
				data.definition.generated = isGenerated(data.definition.id);
				// take snapshot for reset
				_originals[data.definition.id] = _assets[data.definition.id].clone();
			}
		}
		
		private function isGenerated(assetId : String) : Boolean
		{
			var check : Boolean;
			var inputIndex : int = -1;
			var kernelDef : KernelDefinition;
			
		// find first occurence of input
		
		// raptor attack... http://xkcd.com/292/
		// I'll refactor this some day, I swear. Atm I'm just lazy.
		inputLoop:
			for (var i : int = 0; i < _kernelDefinitions.length; i++) {
				kernelDef = _kernelDefinitions[i];
				for (var j : int = 0; j < kernelDef.inputs.length; j++) {
					if (kernelDef.inputs[j].assetId == assetId) {
						inputIndex = i;
						break inputLoop;
					}
				}
			}
			
			// No occurence as input is a generated image
			if (inputIndex == -1) return true;
			
			// if this image is an output before it is an input, it is generated (and cannot be loaded from the GUI)
			for (i = 0; i < inputIndex; i++) {
				kernelDef = _kernelDefinitions[i];
				if (kernelDef.output.assetId == assetId) return true;
			}
			
			return false;
		}
	}
}